package com.flesler.stickjoy.air.wrapper
{
	import com.flesler.stickjoy.mapper.ControllerInputMapper;
	import com.flesler.stickjoy.mapper.ControllerInputMapperEvent;
	
	import flash.desktop.NativeProcess;
	import flash.desktop.NativeProcessStartupInfo;
	import flash.errors.IOError;
	import flash.errors.IllegalOperationError;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.filesystem.File;
	import flash.system.Capabilities;
	
	public final class StickJoy extends EventDispatcher
	{
		private static const JARS_PATH:String = 'libs';
		private static const DLLS_PATH:String = 'native';
		
		private static const SERVER_CLASS:String = 'com.flesler.stickjoy.stdout.StdOutStickJoyWrapper';
		
		private static const JAVA_WIN_PATHS:Array = ['c:/windows/system32/javaw.exe']
		private static const JAVA_PATHS:Array = [
			'/usr/bin/java', '/etc/alternatives/java',
			'/System/Library/Frameworks/JavaVM.framework/Versions/Current/Commands/java'
		];
			
		
		private var isWindows:Boolean;
		private var baseDir:File;
		private var javaRoot:String;
		private var process:NativeProcess;
		private var mapper:ControllerInputMapper;
		
		public function StickJoy(javaRoot:String='./')
		{
			baseDir = rootDir.resolvePath(javaRoot);
				
			init();
		}
		
		private function init():void
		{
			isWindows = Capabilities.os.toLowerCase().indexOf('win') !== -1;
		}
		
		private function get rootDir():File
		{
			return File.applicationDirectory;
		}
		
		private function findJava():File
		{
			var paths:Array = isWindows ? JAVA_WIN_PATHS : JAVA_PATHS;
			for each (var path:String in paths)
			{
				var file:File = new File(path);
				if (file.exists)
					return file;
			}
			
			throw new IOError('StickJoy: Java executable could not be found or run');
		}
		
		private function makeClassPath():String
		{
			var jars:Array = [];
			for each (var jar:File in baseDir.resolvePath(JARS_PATH).getDirectoryListing())
				if (jar.extension === 'jar')
					jars.push(getRelativePath(jar));
			
			return jars.join(isWindows ? ';' : ':')/*.replace(/\\/g, '/')*/;
		}
		
		private function getRelativePath(file:File):String
		{
			var path:String = file.nativePath.replace(baseDir.nativePath, '');
			if (path.charAt(0) === File.separator)
				return path.slice(1);
			return path;
		}
		
		private function makeInfo():NativeProcessStartupInfo
		{
			var info:NativeProcessStartupInfo = new NativeProcessStartupInfo();
			info.executable = findJava();
			info.workingDirectory = baseDir;
			
			var args:Vector.<String> = new Vector.<String>();
			args.push('-cp', makeClassPath());
			args.push('-Djava.library.path='+DLLS_PATH);
			args.push(SERVER_CLASS);
			info.arguments = args;
			
			return info;     
		}
		
		public function start():void
		{
			if (!NativeProcess.isSupported)
				throw new IllegalOperationError('StickJoy: Native process is not supported');
			
			var info:NativeProcessStartupInfo = makeInfo();
			
			process = new NativeProcess();
			process.addEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, notifyMapper);
			process.addEventListener(ProgressEvent.STANDARD_ERROR_DATA, logProcessError);
			process.addEventListener(IOErrorEvent.STANDARD_INPUT_IO_ERROR, handleIoError);
			process.addEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, handleIoError);
			process.addEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, handleIoError);
			
			mapper = new ControllerInputMapper(process.standardOutput);
			mapper.addEventListener(ControllerInputMapperEvent.ADDED, notifyControllerChanged);
			mapper.addEventListener(ControllerInputMapperEvent.REMOVED, notifyControllerChanged);

			process.start(info);
		}
		
		private function handleIoError(e:ErrorEvent):void
		{
			// TODO
			throw new Error(e.text);
		}
		
		private function notifyMapper(e:Event):void
		{
			mapper.read();
		}
		
		private function logProcessError(e:Event):void
		{
			trace('StickJoy - Native Process Error');
			trace(process.standardError.readUTFBytes(process.standardError.bytesAvailable));
		}
		
		private function notifyControllerChanged(e:ControllerInputMapperEvent):void
		{
			var type:String = e.type === ControllerInputMapperEvent.ADDED ? StickJoyEvent.ADDED : StickJoyEvent.REMOVED;
			dispatchEvent(new StickJoyEvent(type, e.controller));
		}
		
		private function destroyProcess():void
		{
			process.removeEventListener(ProgressEvent.STANDARD_OUTPUT_DATA, notifyMapper);
			process.removeEventListener(ProgressEvent.STANDARD_ERROR_DATA, logProcessError);
			process.removeEventListener(IOErrorEvent.STANDARD_INPUT_IO_ERROR, handleIoError);
			process.removeEventListener(IOErrorEvent.STANDARD_OUTPUT_IO_ERROR, handleIoError);
			process.removeEventListener(IOErrorEvent.STANDARD_ERROR_IO_ERROR, handleIoError);
			// TODO: Kill hard for now, try to ask nicely later
			process.exit(true);
			process = null;
		}
		
		private function destroyMapper():void
		{
			mapper.removeEventListener(ControllerInputMapperEvent.ADDED, notifyControllerChanged);
			mapper.removeEventListener(ControllerInputMapperEvent.REMOVED, notifyControllerChanged);
			mapper.destroy();
			mapper = null;
		}
		
		public function destroy():void
		{
			if (process)
				destroyProcess();
			
			if (mapper)
				destroyMapper();
			baseDir = null;
		}
	}
}